// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {decorate, define as crUiDefine} from 'chrome://resources/js/cr/ui.m.js';
import {Splitter} from 'chrome://resources/js/cr/ui/splitter.js';
import {Tree, TreeItem} from 'chrome://resources/js/cr/ui/tree.js';
import {$} from 'chrome://resources/js/util.m.js';

import {getAllNodes} from './chrome_sync.js';

/**
 * A helper function to determine if a node is the root of its type.
 *
 * @param {!Object} node The node to check.
 */
function isTypeRootNode(node) {
  return node.PARENT_ID === 'r' && node.UNIQUE_SERVER_TAG !== '';
}

/**
 * A helper function to determine if a node is a child of the given parent.
 *
 * @param {!Object} parent node.
 * @param {!Object} node The node to check.
 */
function isChildOf(parentNode, node) {
  if (node.PARENT_ID !== '') {
    return node.PARENT_ID === parentNode.ID;
  } else {
    return node.modelType === parentNode.modelType;
  }
}

/**
 * A helper function to sort sync nodes.
 *
 * Sorts by position index if possible, falls back to sorting by name, and
 * finally sorting by METAHANDLE.
 *
 * If this proves to be slow and expensive, we should experiment with moving
 * this functionality to C++ instead.
 */
function nodeComparator(nodeA, nodeB) {
  if (nodeA.hasOwnProperty('positionIndex') &&
      nodeB.hasOwnProperty('positionIndex')) {
    return nodeA.positionIndex - nodeB.positionIndex;
  } else if (nodeA.NON_UNIQUE_NAME !== nodeB.NON_UNIQUE_NAME) {
    return nodeA.NON_UNIQUE_NAME.localeCompare(nodeB.NON_UNIQUE_NAME);
  } else {
    return nodeA.METAHANDLE - nodeB.METAHANDLE;
  }
}

/**
 * Updates the node detail view with the details for the given node.
 * @param {!Object} node The struct representing the node we want to display.
 */
function updateNodeDetailView(node) {
  const nodeDetailsView = $('node-details');
  nodeDetailsView.hidden = false;
  jstProcess(new JsEvalContext(node.entry_), nodeDetailsView);
}

/**
 * Updates the 'Last refresh time' display.
 * @param {string} str The text to display.
 */
function setLastRefreshTime(str) {
  $('node-browser-refresh-time').textContent = str;
}

/**
 * Creates a new sync node tree item.
 *
 * @constructor
 * @param {!Object} node The nodeDetails object for the node as returned by
 *     getAllNodes().
 * @extends {TreeItem}
 */
function SyncNodeTreeItem(node) {
  const treeItem = new TreeItem();
  treeItem.__proto__ = SyncNodeTreeItem.prototype;

  treeItem.entry_ = node;
  treeItem.label = node.NON_UNIQUE_NAME;
  if (node.IS_DIR) {
    treeItem.mayHaveChildren_ = true;

    // Load children on expand.
    treeItem.expanded_ = false;
    treeItem.addEventListener('expand', treeItem.handleExpand_.bind(treeItem));
  } else {
    treeItem.classList.add('leaf');
  }
  return treeItem;
}

SyncNodeTreeItem.prototype = {
  __proto__: TreeItem.prototype,

  /**
   * Finds the children of this node and appends them to the tree.
   */
  handleExpand_(event) {
    const treeItem = this;

    if (treeItem.expanded_) {
      return;
    }
    treeItem.expanded_ = true;

    const children = treeItem.tree.allNodes.filter(
        isChildOf.bind(undefined, treeItem.entry_));
    children.sort(nodeComparator);

    children.forEach(function(node) {
      treeItem.add(new SyncNodeTreeItem(node));
    });
  },
};

/**
 * Creates a new sync node tree.  Technically, it's a forest since it each
 * type has its own root node for its own tree, but it still looks and acts
 * mostly like a tree.
 *
 * @param {Object=} opt_propertyBag Optional properties.
 * @constructor
 * @extends {Tree}
 */
const SyncNodeTree = crUiDefine('tree');

SyncNodeTree.prototype = {
  __proto__: Tree.prototype,

  decorate() {
    Tree.prototype.decorate.call(this);
    this.addEventListener('change', this.handleChange_.bind(this));
    this.allNodes = [];
  },

  populate(nodes) {
    const tree = this;

    // We store the full set of nodes in the SyncNodeTree object.
    tree.allNodes = nodes;

    const roots = tree.allNodes.filter(isTypeRootNode);
    roots.sort(nodeComparator);

    roots.forEach(function(typeRoot) {
      tree.add(new SyncNodeTreeItem(typeRoot));
    });
  },

  handleChange_(event) {
    if (this.selectedItem) {
      updateNodeDetailView(this.selectedItem);
    }
  }
};

/**
 * Clears any existing UI state.  Useful prior to a refresh.
 */
function clear() {
  const treeContainer = $('sync-node-tree-container');
  while (treeContainer.firstChild) {
    treeContainer.removeChild(treeContainer.firstChild);
  }

  const nodeDetailsView = $('node-details');
  nodeDetailsView.hidden = true;
}

/**
 * Fetch the latest set of nodes and refresh the UI.
 */
function refresh() {
  $('node-browser-refresh-button').disabled = true;

  clear();
  setLastRefreshTime('In progress since ' + (new Date()).toLocaleString());

  getAllNodes(function(nodeMap) {
    let nodes = [];
    if (nodeMap && nodeMap.length > 0) {
      // Put all nodes into one big list that ignores the type.
      nodes = nodeMap.map(x => x.nodes).reduce((a, b) => a.concat(b));
    }

    const treeContainer = $('sync-node-tree-container');
    const tree = document.createElement('tree');
    tree.setAttribute('id', 'sync-node-tree');
    tree.setAttribute('icon-visibility', 'parent');
    treeContainer.appendChild(tree);

    decorate(tree, SyncNodeTree);
    tree.populate(nodes);

    setLastRefreshTime((new Date()).toLocaleString());
    $('node-browser-refresh-button').disabled = false;
  });
}

document.addEventListener('DOMContentLoaded', function(e) {
  $('node-browser-refresh-button').addEventListener('click', refresh);
  const customSplitter = crUiDefine('div');

  customSplitter.prototype = {
    __proto__: Splitter.prototype,

    handleSplitterDragEnd(e) {
      Splitter.prototype.handleSplitterDragEnd.apply(this, arguments);
      const treeElement = $('sync-node-tree-container');
      const newWidth = parseFloat(treeElement.style.width);
      treeElement.style.minWidth = Math.max(newWidth, 50) + 'px';
    }
  };

  customSplitter.decorate($('sync-node-splitter'));

  // Automatically trigger a refresh the first time this tab is selected.
  $('sync-browser-tab').addEventListener('selectedChange', function f(e) {
    if (this.selected) {
      $('sync-browser-tab').removeEventListener('selectedChange', f);
      refresh();
    }
  });
});
